package node;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

public class SocketNode {
    ServerSocket serverSocket;
    int id = -1;
    int predecessor, successor;
    Map<Integer, String> data;

    public static void main(String[] args){
        SocketNode socketNode = new SocketNode(1234);
    }
    
    public SocketNode(int id){
        try {
            serverSocket = new ServerSocket(id);
        } catch (IOException e) {
            System.out.println("Error opening ServerSocket with port: " + id);
        }
        predecessor = successor = this.id = id;
        data = new HashMap<Integer, String>();
        run();
    }

    private void run(){
        Socket socket;
        while(true){
            try {
                System.out.println("Waiting for connection: " + id);
                socket = serverSocket.accept();
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
                        socket.getInputStream()));
                BufferedWriter outputWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                String read;
                while((read = bufferedReader.readLine()) != null){
                    handleMessage(outputWriter, read);
                    if(read.equals("")){
                        break;
                    }
                }
                
                bufferedReader.close();
                outputWriter.close();
                socket.close();

            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
    
    private String sendCommandToNode(int nodeId, String command){
        String response = "";
        try {
            Socket socket = new Socket("localhost", nodeId);
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            bufferedWriter.write(command);

            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String read;
            while( (read = bufferedReader.readLine()) != null){
                if(response.length() > 0) response += "\n";
                response += read;
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        return response;
    }

    public int lookup(int k) {
        if (Helper.between(k, predecessor, getId())) {
            return id;
        }
        return Integer.parseInt(sendCommandToNode(successor, "lookup " + k));
    }

    public Map<Integer, String> getAll(int k1, int k2) {
        Map<Integer, String> result = new HashMap<Integer, String>();
        for (Map.Entry<Integer, String> entry : data.entrySet()) {
            if (Helper.between(entry.getKey(), k1, k2)) {
                result.put(entry.getKey(), entry.getValue());
            }
        }
        for (Integer key : result.keySet())
            data.remove(key);
        return result;
    }

    
    
    public enum messages { getId, getAll, get, setSuccessor, getSuccessor, setPredecessor, getPredecessor, put, lookup};

    private void handleMessage(BufferedWriter outputWriter, String message){
        StringTokenizer tokenizer = new StringTokenizer(message);

        try{
            switch (messages.valueOf(tokenizer.nextToken())) {
                case getId:
                    outputWriter.write(getId());
                    break;
                case getAll:
                    int k1 = Integer.parseInt(tokenizer.nextToken());
                    int k2 = Integer.parseInt(tokenizer.nextToken());
                    for (Map.Entry<Integer, String> entry : getAll(k1, k2).entrySet()) {
                        outputWriter.write(entry.getKey() + " " + entry.getValue() + " ");
                    }
                    break;
                case setSuccessor:
                    setSuccessor(Integer.parseInt(tokenizer.nextToken()));
                    break;
                case getSuccessor:
                    outputWriter.write(getSuccessor());
                    break;
                case setPredecessor:
                    setPredecessor(Integer.parseInt(tokenizer.nextToken()));
                    break;
                case getPredecessor:
                    outputWriter.write(getPredecessor());
                    break;
                case get:
                    outputWriter.write(data.get(Integer.parseInt(tokenizer.nextToken())));
                    break;
                case put:
                    int key = Integer.parseInt(tokenizer.nextToken());
                    String value = tokenizer.nextToken();
                    data.put(key, value);
                    break;
                case lookup:
                    outputWriter.write(lookup(Integer.parseInt(tokenizer.nextToken())));
                    break;
            }
            outputWriter.newLine();
        } catch (IllegalArgumentException E){
            System.out.println(E.getMessage());
        } catch (IOException e) {
            System.out.println("Error writing response");
        }


    }

    public int getPredecessor() {
        return predecessor;
    }

    public void setPredecessor(int predecessor) {
        this.predecessor = predecessor;
    }

    public int getSuccessor() {
        return successor;
    }

    public void setSuccessor(int successor) {
        this.successor = successor;
    }

    public int getId() {
        return id;
    }

}
